﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using WimaxSimulator.Properties;

namespace WimaxSimulator.Wimax
{
    /// <summary>
    /// Represents a node in the topology
    /// </summary>
    [Serializable]
    public abstract class Station
    {
        public static float McsCoefficient;
        public static float HopCoefficient;
        public static float RecvCoefficient;
        public static float SentCoefficient;

        private List<Traffic> trafficDestinations = new List<Traffic>();
        public List<Traffic> TrafficDestinations
        {
            get
            {
                if (trafficDestinations == null)
                    trafficDestinations = new List<Traffic>();
                return trafficDestinations;
            }
        }
        public int TotalDLReceived { get; set; }

        public int TotalDLSent { get; set; }

        public int TotalULReceived { get; set; }

        public int TotalULSent { get; set; }

        public abstract uint Ip { get; }
        /// <summary>
        /// Uniquely identifier
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Location of the node in the topology
        /// </summary>
        public Point Location { get; set; }

        /// <summary>
        /// Maximum distance with two nodes to communicate
        /// </summary>
        public int CoverageRadius { get; set; }

        [NonSerialized]
        private Dictionary<uint, uint> homeAgent = new Dictionary<uint, uint>();
        public Dictionary<uint, uint> HomeAgent
        {
            get
            {
                if (homeAgent == null)
                    homeAgent = new Dictionary<uint, uint>();
                return homeAgent;
            }
        }

        [NonSerialized]
        private uint lastIp;
        protected virtual uint LastIp
        {
            get
            {
                if (lastIp == 0)
                    lastIp = Ip;
                ISuperordinate s = (ISuperordinate)this;
                for (uint i = this.Ip + 1; i < this.Ip + Math.Pow(32 - s.IpAddressBlock.Subnet, 2); i++)
                {
                    if ((from sub in s.Subordinates where sub.CareOfAddress == i select sub).Count<ISubordinate>() == 0)
                    {
                        lastIp = i;
                        return lastIp;
                    }
                }

                return 0;
            }
            set
            {
                lastIp = value;
            }
        }

        [NonSerialized]
        private Dictionary<ISuperordinate, long> sentTrafficAnalysis = new Dictionary<ISuperordinate, long>();
        public Dictionary<ISuperordinate, long> SentTrafficAnalysis
        {
            get
            {
                if (sentTrafficAnalysis == null)
                    sentTrafficAnalysis = new Dictionary<ISuperordinate, long>();
                return sentTrafficAnalysis;
            }
        }

        [NonSerialized]
        private Dictionary<ISuperordinate, long> receivedTrafficAnalysis = new Dictionary<ISuperordinate, long>();
        public Dictionary<ISuperordinate, long> ReceivedTrafficAnalysis
        {
            get
            {
                if (receivedTrafficAnalysis == null)
                    receivedTrafficAnalysis = new Dictionary<ISuperordinate, long>();
                return receivedTrafficAnalysis;
            }
        }

        public Station(int Id, Point location, int coverageRadius)
        {
            this.Id = Id;
            Location = location;
            CoverageRadius = coverageRadius;
            if (File.Exists(Id + ".log"))
                File.Delete(Id + ".log");
        }

        /// <summary>
        /// Checks whether given station is in transmission range of this station
        /// </summary>
        /// <param name="s">Neighbor station</param>
        /// <returns></returns>
        public bool InCoverage(Station s)
        {
            if (s != null && s != this)
            {
                return Subtract(Location, s.Location) < CoverageRadius;
            }
            return false;
        }

        public abstract void Receive(Packet p);

        public abstract void Send();

        public static int Subtract(Point p1, Point p2)
        {
            return (int)Math.Sqrt(Math.Pow(Math.Abs(p1.X - p2.X), 2) + Math.Pow(Math.Abs(p1.Y - p2.Y), 2));
        }

        public virtual void Clear()
        {
            TotalULReceived = 0;
            TotalULSent = 0;
            TotalDLReceived = 0;
            TotalDLSent = 0;
            if (File.Exists(Id + ".log"))
                File.Delete(Id + ".log");
            if (this is ISuperordinate)
            {
                ISuperordinate s = (ISuperordinate)this;

                s.DownlinkBandwidth = Settings.Default.DlBw;
                s.Subordinates.Clear();
                s.IpAddressAssignment.Clear();
                s.DownlinkQueue.Clear();
            }
            if (this is ISubordinate)
            {
                ISubordinate su = (ISubordinate)this;
                su.AccessStation = null;
                SentTrafficAnalysis.Clear();
                ReceivedTrafficAnalysis.Clear();
                su.HomeAddress = 0;
                su.CareOfAddress = 0;
                su.UplinkQueue.Clear();
                su.UplinkBandwidth = Settings.Default.MsTotalBw;
            }
            if (this is RelayStation)
                ((RelayStation)this).UplinkBandwidth = 0;
           
        }

        protected double rate(ISuperordinate station)
        {
            double avaliableBandwidth = station.AvaliableBandwidth / Settings.Default.UlBw;
            double load = station.Congestion > 1 ? 1 / Math.Log(station.Congestion, 100) : 1;
            double dist = Math.Min((int)station.MinMCS, (int)getMCS(((Station)station).Location)) / 7.0;
            double hop = (32 - station.IpAddressBlock.Subnet) / 16.0;
            double sent = 0;
            double recv = 0;
            if (SentTrafficAnalysis.ContainsKey(station))
                sent = (double)(SentTrafficAnalysis[station]) / (from traf in SentTrafficAnalysis.Values
                                                                 select traf).Sum();
            if (ReceivedTrafficAnalysis.ContainsKey(station))
                recv = (double)(ReceivedTrafficAnalysis[station]) / (from traf in ReceivedTrafficAnalysis.Values
                                                                     select traf).Sum();
            double cost = Settings.Default.McsCoefficient * dist + Settings.Default.HopCountCoefficient * hop + Settings.Default.SentDataCoefficient * sent + Settings.Default.RecvDataCoefficient * recv + load * Settings.Default.LoadCoefficient + avaliableBandwidth;
            return cost;
        }

        public static bool IsSubordinate(Cidr ip1, uint ip2)
        {
            return ((ip1.Ip ^ ip2) & ip1.SubnetMask) == 0;
        }

        protected void updateTrafficAnalysis(bool ul, uint add, int bw)
        {
            //ISuperordinate d = (from parent in ReceivedTrafficAnalysis.Keys
            //                    where (parent.IpAddressBlock.SubnetMask & p.Source) == parent.IpAddressBlock.Ip
            //                    select parent).SingleOrDefault<ISuperordinate>();
            Dictionary<ISuperordinate, long> dict = ul ? SentTrafficAnalysis : ReceivedTrafficAnalysis;
            ISuperordinate d = (from parent in dict.Keys
                                where (add ^ parent.IpAddressBlock.Ip) == (from pr in dict.Keys select (pr.IpAddressBlock.Ip ^ add)).Min()
                                select parent).FirstOrDefault<ISuperordinate>();
            if (d != null)
            {
                dict[d] += bw;
            }
        }

        public override string ToString()
        {
            return "" + IpToStr(Ip);
        }

        public static string IpToStr(uint ip)
        {
            return (ip >> 24) + "." + ((0x00ff0000 & ip) >> 16) + "." + ((0x0000ff00 & ip) >> 8) + "." + (0x000000ff & ip);
        }

        public virtual uint Connect(ISubordinate subordinateStation)
        {
            uint ip = assignIp(subordinateStation);
            ISuperordinate me = (ISuperordinate)this;


            if (ip == 0)
                return 0;


            if (this is RelayStation)
            {
                ISubordinate mee = ((ISubordinate)this);
                if (!mee.AccessStation.BandwidthRequest(subordinateStation.UplinkBandwidth, true))
                    return 0;
                mee.UplinkBandwidth += subordinateStation.UplinkBandwidth;
                subordinateStation.BaseStation = mee.BaseStation;
            }
            else
            {
                BaseStation meee = (BaseStation)this;
                if (!meee.BandwidthRequest(subordinateStation.UplinkBandwidth, subordinateStation is RelayStation))
                    return 0;
                subordinateStation.BaseStation = meee;
            }

            try
            {
                me.Subordinates.Add(subordinateStation);

                me.DownlinkQueue.Add(subordinateStation, new Queue<Packet>());
            }
            catch (Exception)
            {
                throw;
            }


            return ip;
        }
        protected uint assignIp(ISubordinate subordinateStation)
        {
            ISuperordinate me = (ISuperordinate)this;
            if (subordinateStation is ISuperordinate)
            {
                if (subordinateStation.AccessStation == me)
                {
                    me.IpAddressAssignment.Remove(((ISuperordinate)subordinateStation).IpAddressBlock);
                }
                if (me.IpAddressBlock.Subnet < 29)
                {
                    for (uint i = 1; i < 4; i++)
                    {
                        uint nextBlock = (me.IpAddressBlock.Ip >> (32 - me.IpAddressBlock.Subnet - 2)) + i;
                        nextBlock = nextBlock << (32 - me.IpAddressBlock.Subnet - 2);
                        Cidr c = (from s in me.IpAddressAssignment
                                  where s.Ip == nextBlock
                                  select s).SingleOrDefault<Cidr>();
                        if (c == null)
                        {
                            Cidr assigned = new Cidr();
                            assigned.Ip = nextBlock;
                            assigned.Subnet = me.IpAddressBlock.Subnet + 2;
                            assigned.SubnetMask = me.IpAddressBlock.SubnetMask | (me.IpAddressBlock.SubnetMask >> 2);
                            ((ISuperordinate)subordinateStation).IpAddressBlock = assigned;
                            me.IpAddressAssignment.Add(assigned);
                            return assigned.Ip;
                        }
                    }
                    return 0;
                }
                else
                    return 0;
            }
            uint ip = LastIp;

            if (ip==0)
                return 0;
            subordinateStation.CareOfAddress = ip;
            return subordinateStation.CareOfAddress;

        }

        public virtual void Disconnect(MobileStation mobileStation)
        {
            ISubordinate mob = mobileStation;
            ISuperordinate me = (ISuperordinate)this;
            me.DownlinkQueue.Remove(mob);
            me.Subordinates.Remove(mob);

        }

        public MCS getMCS(Point targetLocation)
        {
            if (SimulationEngine.McsEnabled)
                return (MCS)(7 - Subtract(Location, targetLocation) / 17);
            else
                return MCS.BPSK;
        }
    }

    public enum MCS : int
    {
        BPSK = 1,
        QPSK_1_2 = 2,
        QPSK_3_4 = 3,
        QAM16_1_2 = 4,
        QAM16_3_4 = 5,
        QAM64_1_2 = 6,
        QAM64_3_4 = 7
    }
}
